/*
 * HttpHost.cpp
 *
 */

#include "../../common/LogFile.h"
#include "../../common/ServerConfig.h"
#include "HttpAuthentication.h"
#include "HttpHost.h"
#include "HttpResponse.h"
#include "HttpFileBody.h"
#include "HttpStringBody.h"
#include "HttpGet.h"
#include "HttpPost.h"
#include "../Process.h"
#include "UrlEncode.h"
#include "TagManager.h"
#include "HttpParser.h"
#include "HttpCodes.h"

const std::string HttpHost::VERSION = "HTTP/1.1";

HostException::HostException(const std::string& msg) throw () :
	msg(msg) {
}

HttpHost::HttpHost(const ServerConfig& config, LogFile& logFile) :
	config(config),
	logFile(logFile) {
}

HttpHost::~HttpHost() {}

void HttpHost::generateResponse(HttpResponse& response,
		const HttpRequest& request) {
	if (authenticated(response, request))
		if (!staticProcess(response, request))
			if (!dynamicProcess(response, request))
				errorProcess(response, HttpCodes::NOT_FOUND);
}

bool HttpHost::staticProcess(HttpResponse& response,
		const HttpRequest& request) {
	bool responseGenerated = false;
	std::string resource, params;
	UrlEncode::splitUri(request.getUri(), resource, params);
	std::string ext = resource.substr(resource.find_last_of('.') + 1);
	try {
		std::string type = config.getStaticType(ext);
		HttpBody* body = new HttpFileBody(resourcePath(resource));
		response.setCode(HttpCodes::OK);
		response.addHeader(new HttpHeader(HttpHeader::CONTENT_TYPE, type));
		response.setBody(body);
		responseGenerated = true;
		logFile.addAccessInfo(request.getRequestLine(), response.getCode());
	}
	catch (const MessageException& e) {
		errorProcess(response, HttpCodes::NOT_FOUND); // no encontrado
		responseGenerated = true;
		logFile.addAccessError(request.getRequestLine(), response.getCode());
	}
	catch (const std::string& s) {}
	return responseGenerated;
}

bool HttpHost::dynamicProcess(HttpResponse& response,
		const HttpRequest& request) {
	bool responseGenerated = false;
	std::string resource, params;
	UrlEncode::splitUri(request.getUri(), resource, params);
	std::string ext = resourceExtension(resource);
	std::string filename = resourcePath(resource);
	try {
		std::string command = config.getDynamicType(ext);
		TagManager::replace("{NAME}", filename, command);
		process.setCommand(command);
		process.addEnvVar("REDIRECT_STATUS", "1");
		process.addEnvVar("DOCUMENT_ROOT", config.getRootPath());
		process.addEnvVar("SERVER_PROTOCOL", "HTTP/1.1");
		process.addEnvVar("SCRIPT_FILENAME", filename);
		process.addEnvVar("GATEWAY_INTERFACE", "CGI/1.1");
		process.addEnvVar("REQUEST_URI", resource);
		process.addEnvVar("SCRIPT_NAME", resource);
		process.addEnvVar("REQUEST_METHOD", request.getMethod());

		std::vector<char> buffer(DATA_BUFFER_SIZE);
		size_t read;
		if (request.getMethod() == HttpRequest::GET) {
			process.addEnvVar("QUERY_STRING", params);
			process.exe();
		}
		else if (request.getMethod() == HttpRequest::POST) {
			process.addEnvVar("CONTENT_TYPE",
					request.getHeader(HttpHeader::CONTENT_TYPE));
			process.addEnvVar("CONTENT_LENGTH",
					request.getHeader(HttpHeader::CONTENT_LENGTH));

			process.exe();

			size_t size = request.getBody().getSize();
			std::vector<char> buffer(size);
			request.getBody().getStream().read(buffer.data(), buffer.size());
			read = request.getBody().getStream().gcount();
			while (read > 0) {
				process.getCin().write(buffer.data(), read);
				request.getBody().getStream().read(buffer.data(), buffer.size());
				read = request.getBody().getStream().gcount();
			}
		}
		std::stringstream data;
		read = process.getCout().read(buffer.data(), buffer.size());
		while (read > 0) {
			data.write(buffer.data(), read);
			read = process.getCout().read(buffer.data(), buffer.size());
		}
		process.wait(); // espero a que termine

		response.setCode(HttpCodes::OK);
		// levanto los headers
		std::string line;
		std::getline(data, line);
		while (line.length() > 0 && line[0] != HttpMessage::CR) {
			response.addHeader(HttpParser::parseHeader(line));
			std::getline(data, line);
		}
		// levanto el body
		std::stringstream body;
		body << data.rdbuf();
		response.setBody(new HttpStringBody(body.str()));
		responseGenerated = true;
		logFile.addAccessInfo(request.getRequestLine(), response.getCode());
	}
	catch (const MessageException& e) {
		errorProcess(response, HttpCodes::BAD_REQUEST); // no encontrado
		responseGenerated = true;
		logFile.addAccessError(request.getRequestLine(), response.getCode());
	}
	catch (const std::string& s) {}
	return responseGenerated;
}

void HttpHost::errorProcess(HttpResponse& response, int code) {
	response.setCode(code);
	try {
		std::string resource = config.getErrorHandler(
				StrUtils::toString(response.getCode()));
		std::string ext = resource.substr(resource.find_last_of('.') + 1);
		std::string type = config.getStaticType(ext);
		HttpBody* body = new HttpFileBody(resourcePath(resource));
		response.addHeader(new HttpHeader(HttpHeader::CONTENT_TYPE, type));
		response.setBody(body);
	}
	catch (...) {
		// si hubo algun error entonces se devuelve solo el encabezado
	}
}

bool HttpHost::authenticated(HttpResponse& response,
		const HttpRequest& request) {
	bool authenticated = true;
	if (config.getProtectionFlag()) {
		HttpAuthentication client(request, config);
		if (!client.isAuthenticated()) {
			errorProcess(response, HttpCodes::UNAUTHORIZED);
			HttpHeader* h = new HttpHeader("WWW-Authenticate",
					"Basic realm=\"Taller 2011\"");
			response.addHeader(h);
			authenticated = false;
			logFile.addAccessError(request.getRequestLine(), response.getCode());
		}
	}
	return authenticated;
}

void HttpHost::terminate() {
	process.kill();
}

std::string HttpHost::resourcePath(const std::string& resource) const {
	unsigned int firstBar = resource.find_first_of('/');
	return config.getRootPath() + resource.substr(firstBar);
}

std::string HttpHost::resourceExtension(const std::string& resource) const {
	return resource.substr(resource.find_last_of('.') + 1);
}
